//------------------------------------------------------------------------------
// X2TK Graphic Toolkit
// Copyright (C) 2006-2008 Janusz Kowalski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include "x2tk_Graphics.h"
#include "SDL_gfxPrimitives.h"
#include "pmsg.h"

#ifdef X2D_WITH_OPENGL
#ifdef OS_WINDOWS
#include <windows.h>
#endif
#include "GL/gl.h"
#include "GL/glu.h"
#endif

//!! FIXME: Make it more precise and globally available.
#ifdef MSVC
#define M_PI 3.141592654
#endif

GraphicContext::GraphicContext(SDL_Surface *dstSurf) {
  dstSurf_ = dstSurf;
  cRect_.x = 0; cRect_.y =  0;
  cRect_.w = dstSurf_->w; cRect_.h = dstSurf_->h;
  ox_=0; oy_=0;
  clipToRectangle_=true;
}

GraphicContext::~GraphicContext() {
}

// !! Needs improvements.
void GraphicContext::setClipping(bool v) {
  if(v) {
#ifdef X2D_WITH_OPENGL
    glEnable(GL_SCISSOR_TEST);
#endif
  } else {
#ifdef X2D_WITH_OPENGL
    glDisable(GL_SCISSOR_TEST);
#endif
  }
}

 void GraphicContext::startOpenGL() {
   glPushMatrix();
   glTranslatef(static_cast<GLfloat>(ox_), static_cast<GLfloat>(oy_), 0);
   if(clipToRectangle_) {
     glScissor(cRect_.x, cRect_.y, cRect_.w, cRect_.h);
     glEnable(GL_SCISSOR_TEST);
   }
 }
 
 void GraphicContext::endOpenGL() {
   glPopMatrix();
 }

void GraphicContext::clipRectangle(SDL_Rect& victim, int x, int y, int w, int h) const {
  if(x>victim.x) {
    if(x<victim.x+victim.w) {
      victim.w += victim.x-x;
      victim.x = x;
    } else victim.w=0;
  }
  
  if(y>victim.y) {
    if(y<victim.y+victim.h) {
      victim.h += victim.y-y;
      victim.y = y;
    } else victim.h=0;
  }
  
  // Clip victim from bottom-right.
  if(x+w<victim.x+victim.w) {
    if(x+w>victim.x) {
      victim.w = x+w-victim.x;
    } else victim.w=0;
  }
  
  if(y+h<victim.y+victim.h) {
    if(y+h>victim.y) {
      victim.h = y+h-victim.y;
    } else victim.h=0;
  }
}

void GraphicContext::limit(int x_rel, int y_rel, int w, int h) {
//   // Change relative values into absolute ones.
//   int x = ox_ + x_rel;
//   int y = oy_ + y_rel;
//   
//   clipRectangle(cRect_,x,y,w,h);
//   // Set origin values.
//   ox_ = x;
//   oy_ = y;
  ox_+=x_rel; oy_+=y_rel;
  clipRectangle(cRect_,ox_,oy_,w,h);
}

void GraphicContext::drawCircle(float x0, float y0, int r, int l) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    // !! Missing code here.
#ifdef X2D_WITH_OPENGL
  } else {
    float dAngle = 2 * asin(float(l)/float(2*r));
    glColor3ub(255,0,0);
    glDisable(GL_TEXTURE_2D);

    glBegin(GL_POLYGON);
    for(float angle=0; angle + dAngle < 2*M_PI; angle+=dAngle) {
      float x = r*cos(angle) + x0 +ox_, y = r*sin(angle) + y0 +oy_;
      glVertex2f(x,y);
    }
    glEnd();

    glColor3ub(0,0,0);
    glBegin(GL_LINE_LOOP);
    for(float angle=0; angle + dAngle < 2*M_PI; angle+=dAngle) {
      float x = r*cos(angle) + x0 +ox_, y = r*sin(angle) + y0 +oy_;
      glVertex2f(x,y);
    }
    glEnd();
  }
#endif
}

// !! Take rotation into account.
void GraphicContext::drawTexturedCircle(Image &image, float x0, float y0, float r, float angle, int l) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    // !! Missing code here.
#ifdef X2D_WITH_OPENGL
  } else {
    float angleDelta = 2 * asin(float(l)/float(2*r));
    glColor3ub(255,0,0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    glPushMatrix();
    glTranslatef(static_cast<GLfloat>(x0+ox_), static_cast<GLfloat>(y0+oy_), 0);
    glRotatef(static_cast<GLfloat>(angle*180/M_PI),0,0,1);
    glBegin(GL_POLYGON);
    for(float angle=0; angle + angleDelta < 2*M_PI; angle+=angleDelta) {
      float xx = cos(angle), yy = sin(angle);
      glTexCoord2f(0.5f*xx+0.5f,0.5f*yy+0.5f);
      glVertex2f(r*xx,r*yy);
    }
    glEnd();
    glPopMatrix();
  }
#endif
}

void GraphicContext::blitImage(Image &image, int x, int y) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    blitSurface(image.getSurf(),x,y);
#ifdef X2D_WITH_OPENGL
  } else {
    x+=ox_; y+=oy_;
    
    glColor3ub(255,0,0);
    //rectangle(cRect_.x-ox_, cRect_.y-oy_, cRect_.w, cRect_.h);
    
    glEnable(GL_SCISSOR_TEST);
    glScissor(cRect_.x, dstSurf_->h - cRect_.h - cRect_.y, cRect_.w, cRect_.h);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);  glVertex2d(x,y);
    glTexCoord2i(1,0);  glVertex2d(x+image.getW(),y);
    glTexCoord2i(1,1);  glVertex2d(x+image.getW(),y+image.getH());
    glTexCoord2i(0,1);  glVertex2d(x,y+image.getH());
    glEnd();
    glDisable(GL_SCISSOR_TEST);
  }
#endif
}

void GraphicContext::drawImage(Image &image, float x, float y) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
  // !! No support for SDL.
#ifdef X2D_WITH_OPENGL
  } else {
    x+=ox_; y+=oy_;
    glEnable(GL_SCISSOR_TEST);
    glScissor(cRect_.x, dstSurf_->h - cRect_.h - cRect_.y, cRect_.w, cRect_.h);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);  glVertex2d(x,y);
    glTexCoord2i(1,0);  glVertex2d(x+image.getW(),y);
    glTexCoord2i(1,1);  glVertex2d(x+image.getW(),y+image.getH());
    glTexCoord2i(0,1);  glVertex2d(x,y+image.getH());
    glEnd();
    glDisable(GL_SCISSOR_TEST);
  }
#endif
}

void GraphicContext::TMPblitImage(Image &image, int x, int y, float angle) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    // !! Not supported yet!
#ifdef X2D_WITH_OPENGL
  } else {
    glColor3ub(255,0,0);
    //rectangle(cRect_.x-ox_, cRect_.y-oy_, cRect_.w, cRect_.h);
    
    glEnable(GL_SCISSOR_TEST);
    glScissor(cRect_.x, dstSurf_->h - cRect_.h - cRect_.y, cRect_.w, cRect_.h);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    //glMatrixMode(GL_PROJECTION); glPushMatrix();e(GL_TEXTURE_2D, image.getTex());
    
    
    //glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glTranslatef(static_cast<GLfloat>(x+ox_), static_cast<GLfloat>(y+oy_), 0);
    glRotatef(static_cast<GLfloat>(angle*180/M_PI),0,0,1);
    
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);  glVertex2d(0,0);
    glTexCoord2i(1,0);  glVertex2d(0+image.getW(),0);
    glTexCoord2i(1,1);  glVertex2d(0+image.getW(),0+image.getH());
    glTexCoord2i(0,1);  glVertex2d(0,0+image.getH());
    glEnd();
    
    glPopMatrix();
    //glMatrixMode(GL_PROJECTION); glPopMatrix();
    glDisable(GL_SCISSOR_TEST);
  }
#endif
}

void GraphicContext::blitImage(Image &image, SDL_Rect *srcRect, int x, int y) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    blitSurface(image.getSurf(),srcRect,x,y);
#ifdef X2D_WITH_OPENGL
  } else {
    float tx1 = srcRect->x/(float)image.getW();
    float tx2 = (srcRect->x+srcRect->w)/(float)image.getW();
    float ty1 = srcRect->y/(float)image.getH();
    float ty2 = (srcRect->y+srcRect->h)/(float)image.getH();
    
    x+=ox_; y+=oy_;
    
    glEnable(GL_SCISSOR_TEST);
    glScissor(cRect_.x, dstSurf_->h - cRect_.h - cRect_.y, cRect_.w, cRect_.h);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    glBegin(GL_QUADS);
    glTexCoord2f(tx1,ty1);  glVertex2d(x,y);
    glTexCoord2f(tx2,ty1);  glVertex2d(x+srcRect->w,y);
    glTexCoord2f(tx2,ty2);  glVertex2d(x+srcRect->w,y+srcRect->h);
    glTexCoord2f(tx1,ty2);  glVertex2d(x,y+srcRect->h);
    glEnd();
    glDisable(GL_SCISSOR_TEST);
  }
#endif
}

void GraphicContext::blitImage(Image &image, int x, int y, int w, int h) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    // !! This doesn't take scaling into account.
    blitSurface(image.getSurf(),x,y);
#ifdef X2D_WITH_OPENGL
  } else {
    x+=ox_; y+=oy_;
    
    glEnable(GL_SCISSOR_TEST);
    glScissor(cRect_.x, dstSurf_->h - cRect_.h - cRect_.y, cRect_.w, cRect_.h);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, image.getTex());
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);  glVertex2d(x,y);
    glTexCoord2i(1,0);  glVertex2d(x+w,y);
    glTexCoord2i(1,1);  glVertex2d(x+w,y+h);
    glTexCoord2i(0,1);  glVertex2d(x,y+h);
    glEnd();
    glDisable(GL_SCISSOR_TEST);
  }
#endif
}

void GraphicContext::blitSurface(SDL_Surface *src, SDL_Rect *srcRect, int x, int y) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    SDL_Rect dstRect;
    dstRect.x = ox_ + x; dstRect.y = oy_ + y;
  
    SDL_SetClipRect(dstSurf_, &cRect_);
    SDL_BlitSurface(src, srcRect, dstSurf_, &dstRect);
    SDL_SetClipRect(dstSurf_, 0);
#ifdef X2D_WITH_OPENGL
  } else {
    throw "ERROR: blitSurface() called in the OpenGL mode!";
  }
#endif
}

void GraphicContext::blitSurface(SDL_Surface *src, int x, int y) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    SDL_Rect dstRect;
    dstRect.x = ox_ + x; dstRect.y = oy_ + y;
  
    SDL_SetClipRect(dstSurf_, &cRect_);
    SDL_BlitSurface(src, 0, dstSurf_, &dstRect);
    SDL_SetClipRect(dstSurf_, 0);
#ifdef X2D_WITH_OPENGL
  } else {
    throw "ERROR: blitSurface() called in the OpenGL mode!";
  }
#endif
}

// !! This function could be improved!
void GraphicContext::textureFill(SDL_Surface *tex) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    SDL_SetClipRect(dstSurf_, &cRect_);
    for(int y=oy_; y<cRect_.y+cRect_.h; y+=tex->h) {
      for(int x=ox_; x<cRect_.x+cRect_.w; x+=tex->w) {
        SDL_Rect dstRect;
        dstRect.x=x; dstRect.y=y;
        SDL_BlitSurface(tex,0,dstSurf_,&dstRect);
      }
    }
    SDL_SetClipRect(dstSurf_, 0);
#ifdef X2D_WITH_OPENGL
  } else {
  }
#endif
}

void GraphicContext::colorFill(int r, int g, int b) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    SDL_Rect dstRect = cRect_;
    SDL_FillRect(dstSurf_, &dstRect, SDL_MapRGB(dstSurf_->format,r,g,b));
#ifdef X2D_WITH_OPENGL
  } else {
    // !! Color fill not implemented yet.
  }
#endif
}

void GraphicContext::colorFill(int r, int g, int b, int x0, int y0, int w, int h) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    SDL_Rect dstRect;
    dstRect.x = ox_ + x0; dstRect.y = oy_ + y0; dstRect.w=w; dstRect.h=h;
  
    SDL_SetClipRect(dstSurf_, &cRect_);
    SDL_FillRect(dstSurf_, &dstRect, SDL_MapRGB(dstSurf_->format,r,g,b));
    SDL_SetClipRect(dstSurf_, 0);
#ifdef X2D_WITH_OPENGL
  } else {
  }
#endif
}

void GraphicContext::textureFill(SDL_Surface *tex, int x0, int y0, int w, int h) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    // !! This could be done in a more efficient way.
    SDL_Rect ccRect = cRect_;
    clipRectangle(ccRect,ox_+x0,oy_+y0,w,h);
    
    SDL_SetClipRect(dstSurf_, &ccRect);
    for(int y=oy_+y0; y<ccRect.y+ccRect.h; y+=tex->h) {
      for(int x=ox_+x0; x<ccRect.x+ccRect.w; x+=tex->w) {
        SDL_Rect dstRect;
        dstRect.x=x; dstRect.y=y;
        SDL_BlitSurface(tex,0,dstSurf_,&dstRect);
      }
    }
    // !! Code here!
    SDL_SetClipRect(dstSurf_, 0);
#ifdef X2D_WITH_OPENGL
  } else {
  }
#endif
}

// !! This function ignores clipping rectangle.
void GraphicContext::filledPolygon(Sint16 *vx, Sint16 *vy, int n) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    for(int i=n;i--;) {
      vx[i]+=ox_; vy[i]+=oy_;
    }
    gfx::filledPolygonColor(dstSurf_, vx, vy, n, color_);
    for(int i=n;i--;) {
      vx[i]-=ox_; vy[i]-=oy_;
    }
#ifdef X2D_WITH_OPENGL
  } else {
    
  }
#endif
}

// !! This function ignores clipping rectangle.
void GraphicContext::hLine(int x, int y, int w) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    if(w<0) { x+=w; w=-w; }
    
    SDL_LockSurface(dstSurf_);
    gfx::xhlineColor(dstSurf_,x+ox_,y+oy_,w,color_,alpha_);
    SDL_UnlockSurface(dstSurf_);
#ifdef X2D_WITH_OPENGL
  } else {
  }
#endif
}

void GraphicContext::line(int x1, int y1, int x2, int y2) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    gfx::lineColor(dstSurf_, x1+ox_, y1+oy_, x2+ox_, y2+oy_, color_);
#ifdef X2D_WITH_OPENGL
  } else {
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_LINES);
    glVertex2d(x1+ox_,y1+oy_);
    glVertex2d(x2+ox_,y2+oy_);
    glEnd();
  }
#endif
}

void GraphicContext::filledRectangle(int x0, int y0, int w, int h) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    if(w<0) { x0+=w; w=-w; }
    if(h<0) { y0+=h; h=-h; }
    
    // !! Skip drawing when w==0 or h==0.
    SDL_LockSurface(dstSurf_);
    gfx::xboxColor(dstSurf_,x0+ox_,y0+oy_,w,h,color_,alpha_);
    SDL_UnlockSurface(dstSurf_);
#ifdef X2D_WITH_OPENGL
  } else {
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);
    glVertex2d(x0+ox_,y0+oy_);
    glVertex2d(x0+ox_,y0+h+oy_);
    glVertex2d(x0+w+ox_,y0+h+oy_);
    glVertex2d(x0+w+ox_,y0+oy_);
    
    glEnd();
  }
#endif
}

void GraphicContext::rectangle(int x0, int y0, int w, int h) const {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    if(w<0) { x0+=w; w=-w; }
    if(h<0) { y0+=h; h=-h; }
    
    // !! Check for special cases, like points and v/h lines.
    // !! Skip drawing when w==0 or h==0.
    SDL_LockSurface(dstSurf_);
    gfx::xhlineColor(dstSurf_,x0+ox_,y0+oy_,w,color_,alpha_);
    gfx::xhlineColor(dstSurf_,x0+ox_,y0+oy_+h-1,w,color_,alpha_);
    gfx::xvlineColor(dstSurf_,x0+ox_,y0+oy_,h,color_,alpha_);
    gfx::xvlineColor(dstSurf_,x0+ox_+w-1,y0+oy_,h,color_,alpha_);
    SDL_UnlockSurface(dstSurf_);
#ifdef X2D_WITH_OPENGL
  } else {
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_LINE_LOOP);
    glVertex2d(x0+ox_,y0+oy_);
    glVertex2d(x0+ox_,y0+h+oy_);
    glVertex2d(x0+w+ox_,y0+h+oy_);
    glVertex2d(x0+w+ox_,y0+oy_);
    glEnd();
  }
#endif
}

void GraphicContext::setColor(Uint32 color) {
  Uint8 *colorptr = (Uint8 *) &color;
  if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
    setColor(colorptr[0], colorptr[1], colorptr[2], colorptr[3]);
  } else {
    setColor(colorptr[3], colorptr[2], colorptr[1], colorptr[0]);
  }
}

void GraphicContext::setColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
#ifdef X2D_WITH_OPENGL
  if((dstSurf_->flags & SDL_OPENGL) == 0) {
#endif
    r_=r; g_=g; b_=b; alpha_=a;
    color_ = SDL_MapRGBA(dstSurf_->format, r_, g_, b_, alpha_);
#ifdef X2D_WITH_OPENGL
  } else {
    glColor4ub(r,g,b,a);
  }
#endif
}
